using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ValidationFramework.Web.ASP
{
    /// <summary>
    /// Wraps <see cref="PropertyValidatorGenerator"/> functionality in a <see cref="Control"/> to make it easier to use on the design surface.
    /// </summary>
    /// <seealso cref="AddAssociation(Control,string)"/>
    /// <seealso cref="AddAssociation(Control,string, Control)"/>
    /// <seealso cref="GenerateValidators"/>
    public class PropertyValidatorGeneratorControl : Control
    {
        #region Fields

        private ValidatorDisplay display = ValidatorDisplay.Dynamic;
        private Mode mode = Mode.ClientServer;

        private EventHandler<TargetObjectRequiredEventArgs> targetObjectRequired;
        private string typeToValidate;
        private readonly ValidationAssociationCollection validationAssociations;
        private string validationGroup;

        #endregion


        #region Constructors

        /// <summary>
        /// Initialize a new instance of the <see cref="PropertyValidatorGeneratorControl"/> class.
        /// </summary>
        public PropertyValidatorGeneratorControl()
        {
            validationAssociations = new ValidationAssociationCollection();
        }

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets a <see cref="ValidationAssociationCollection"/> containing all the <see cref="ValidationAssociation"/>s for this <see cref="PropertyValidatorGeneratorControl"/>.
        /// </summary>
        public ValidationAssociationCollection ValidationAssociations
        {
            get;
            set;
        }


        /// <summary>
        /// Gets or sets a the type of validators that will be generated. Client Server or both.
        /// </summary>
        /// <remarks>If <see cref="Mode"/> is set to <see cref="Web.Mode.Client"/> it is not necessary to attach to the <see cref="TargetObjectRequired"/> event. Not all <see cref="Rule"/>s can be validated on the client side so your business layer will be responsible for doing a validation of the object on the server side and displaying the message to the user.</remarks>
        [Description("Type of validators that will be generated. Client Server or both.")]
        public Mode Mode
        {
            get
            {
                var validationModeViewState = ViewState["ValidationMode"];
                if (validationModeViewState == null)
                {
                    return mode;
                }
                else
                {
                    return (Mode) validationModeViewState;
                }
            }
            set
            {
                mode = value;
                ViewState["ValidationMode"] = value;
            }
        }


        /// <summary>
        /// Gets or sets the fully qualified name of the target <see cref="Type"/> to validate.
        /// </summary>
        [Description("The fully qualified name of the target Type to validate.")]
        public string TypeToValidate
        {
            get
            {
                var viewStateTypeToValidate = ViewState["TypeToValidate"];
                if (viewStateTypeToValidate == null)
                {
                    return typeToValidate;
                }
                else
                {
                    return (string) viewStateTypeToValidate;
                }
            }
            set
            {
                typeToValidate = value;
                ViewState["TypeToValidate"] = value;
            }
        }


        /// <summary>
        /// Gets or sets the name of the validation group to which all the <see cref="BaseValidator.ValidationGroup"/>s will default to. 
        /// </summary>
        [Description("The name of the validation group to which all the BaseValidators will default to.")]
        public virtual string ValidationGroup
        {
            get
            {
                var viewStateValidationGroup = ViewState["ValidationGroup"];
                if (viewStateValidationGroup == null)
                {
                    return validationGroup;
                }
                else
                {
                    return (string) viewStateValidationGroup;
                }
            }
            set
            {
                validationGroup = value;
                ViewState["ValidationGroup"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the display behavior of the error message to which all the <see cref="BaseValidator.Display"/>s will default to. The default value is <see cref="ValidatorDisplay.Dynamic"/>. 
        /// </summary>
        [Description("The display behavior of the error message to which all the BaseValidators will default to.")]
        public ValidatorDisplay Display
        {
            get
            {
                var validatorDisplay = ViewState["Display"];
                if (validatorDisplay == null)
                {
                    return display;
                }
                else
                {
                    return (ValidatorDisplay) validatorDisplay;
                }
            }
            set
            {
                display = value;
                ViewState["Display"] = value;
            }
        }

        #endregion


        #region Events

        /// <summary>
        /// Occurs when the <see cref="PropertyValidatorGeneratorControl"/> requires a target object for validation. 
        /// </summary>
        public event EventHandler<TargetObjectRequiredEventArgs> TargetObjectRequired
        {
            add
            {
                targetObjectRequired = value;
            }
            remove
            {
                targetObjectRequired = value;
            }
        }

        #endregion


        #region Methods

        /// <summary>
        /// A shortcut method to add a new <see cref="ValidationAssociation"/> to <see cref="ValidationAssociations"/>.
        /// </summary>
        /// <param name="controlToValidate">The <see cref="Control"/> to validate.</param>
        /// <param name="propertyName">The name of the property to validate.</param>
        /// <param name="containerControl">A <see cref="Control"/> to use to locate where the <see cref="BaseValidator"/> will be rendered. Null is accepted.</param>
        /// <returns>The <see cref="ValidationAssociation"/> that has been added to <see cref="ValidationAssociations"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="controlToValidate"/> is a null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="propertyName"/> is a null reference.</exception>
        /// <exception cref="ArgumentException"><paramref name="propertyName"/> is <see cref="string.Empty"/>.</exception>
        public ValidationAssociation AddAssociation(Control controlToValidate, string propertyName, Control containerControl)
        {
            Guard.ArgumentNotNull(controlToValidate, "controlToValidate");
            Guard.ArgumentNotNullOrEmptyString(propertyName, "propertyName");
            var association = new ValidationAssociation(controlToValidate, propertyName, containerControl);
            validationAssociations.Add(association);
            return association;
        }


        /// <summary>
        /// A shortcut method to add a new <see cref="ValidationAssociation"/> to <see cref="ValidationAssociations"/>.
        /// </summary>
        /// <param name="controlToValidate">The <see cref="Control"/> to validate.</param>
        /// <param name="propertyName">The name of the property to validate.</param>
        /// <returns>The <see cref="ValidationAssociation"/> that has been added to <see cref="ValidationAssociations"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="controlToValidate"/> is a null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="propertyName"/> is a null reference.</exception>
        /// <exception cref="ArgumentException"><paramref name="propertyName"/> is <see cref="string.Empty"/>.</exception>
        public ValidationAssociation AddAssociation(Control controlToValidate, string propertyName)
        {
            return AddAssociation(controlToValidate, propertyName, null);
        }


        /// <summary>
        /// Go through all the <see cref="ValidationAssociations"/> and add <see cref="BaseValidator"/>s to each.
        /// </summary>
        public IList<BaseValidator> GenerateValidators()
        {
            return GetValidatorGenerator().GenerateValidators();
        }


        /// <summary>
        /// Get a <see cref="PropertyValidatorGenerator"/>. for this <see cref="PropertyValidatorGeneratorControl"/>.
        /// </summary>
        public PropertyValidatorGenerator GetValidatorGenerator()
        {
            var validatorGenerator = new PropertyValidatorGenerator
                                                                {
                                                                    TypeToValidate = Type.GetType(TypeToValidate, true).TypeHandle,
                                                                    Display = display,
                                                                    Mode = mode,
                                                                    ValidationGroup = validationGroup,
                                                                    ValidationAssociations = validationAssociations,
                                                                    TargetObjectRequired = targetObjectRequired
                                                                };
            return validatorGenerator;
        }

        #endregion
    }
}